Skip to content

Add dual CJS/ESM build#326

Open
tomassabol wants to merge 2 commits into
jeremydaly:mainfrom
tomassabol:feat/esm-dual-package
Open

Add dual CJS/ESM build#326
tomassabol wants to merge 2 commits into
jeremydaly:mainfrom
tomassabol:feat/esm-dual-package

Conversation

@tomassabol

Copy link
Copy Markdown

Summary

This PR adds dual CommonJS and ESM support.

Until now, the package has only shipped as CommonJS, which can make modern ESM bundling workflows awkward. In particular, users bundling Lambda handlers to .mjs with tools like esbuild can hit dynamic require() issues, as described in #295.

This change keeps the existing CommonJS API working while adding a proper ESM build and package export map.

What Changed

  • Moves source files under src/ and builds published output into dist/cjs and dist/esm with SWC
  • Adds conditional package exports for both require() and import
  • Preserves supported deep imports like lambda-api/lib/utils
  • Adds SWC build configs for CJS and ESM output
  • Adds module compatibility tests covering:
    • CommonJS root import
    • CommonJS deep imports
    • ESM default import
    • ESM named/deep module imports
    • basic route execution from both builds

Why

This should make lambda-api friendlier for modern Lambda projects using ESM, .mjs entrypoints, or bundlers like esbuild, while remaining backwards compatible for existing CommonJS users.

@tomassabol tomassabol marked this pull request as ready for review June 16, 2026 18:09
@naorpeled

Copy link
Copy Markdown
Collaborator

Hey @tomassabol,
Thanks for this!

Can you please resolve the CI errors? 🙏

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a dual-build distribution strategy for lambda-api, adding first-class ESM output while preserving CommonJS compatibility via conditional exports and SWC-built artifacts in dist/cjs and dist/esm.

Changes:

  • Migrate source to src/ and compile to dist/cjs + dist/esm using SWC.
  • Add an exports map to support require()/import (including deep ./lib/* subpaths).
  • Add module-compatibility tests validating CJS + ESM loading and basic runtime behavior.

Reviewed changes

Copilot reviewed 20 out of 23 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/package.json Marks src scope as ESM (type: module) to drive ESM compilation behavior.
src/index.js Converts entrypoint to ESM syntax and adds CJS fallback export logic.
src/lib/utils.js Converts utilities to ESM exports/imports and removes CJS exports.* usage.
src/lib/statusCodes.js Switches to ESM default export with CJS fallback assignment.
src/lib/s3-service.js Adds new ESM S3 service implementation intended for both builds.
src/lib/response.js Converts response module to ESM and updates S3 usage sites.
src/lib/request.js Converts request module to ESM default export with CJS fallback.
src/lib/prettyPrint.js Switches to ESM default export with CJS fallback assignment.
src/lib/mimemap.js Switches to ESM default export with CJS fallback assignment.
src/lib/logger.js Converts logger module exports to ESM named exports.
src/lib/errors.js Converts error exports to ESM named exports.
src/lib/compression.js Converts compression module to ESM named export(s).
package.json Adds SWC build scripts, exports map, and publishes dist/ output.
lib/s3-service.js Removes legacy root lib/ S3 service (source now under src/).
jest.config.js Adds Jest mapping to test against built dist/cjs artifacts.
index.test-d.ts Updates type tests to validate default factory typing alignment.
.swcrc.esm.json Adds SWC config for ESM output build.
.swcrc.cjs.json Adds SWC config for CJS output build.
.gitignore Ignores generated dist/ output.
.eslintrc.json Adjusts ESLint parsing defaults with ESM overrides for src/**/*.js and __tests__/**/*.mjs.
tests/module-compat.unit.js Adds CJS/exports-resolution compatibility tests.
tests/esm-compat.mjs Adds Node ESM runtime compatibility smoke test for the ESM build.
Comments suppressed due to low confidence (6)

src/index.js:14

  • Importing the S3 helper at module load time makes the optional AWS SDK peerDependencies effectively required. Since src/lib/s3-service.js imports @aws-sdk/* at top-level, this static import can throw on startup for users who do not install the optional peers (even if they never use S3 features). Consider loading/configuring S3 lazily from response methods instead.
    src/index.js:53
  • This eager S3 client configuration will also break if the optional AWS SDK peerDependencies are not installed (and if S3 is no longer statically imported). Since S3 is only needed for getLink/sendFile with s3:// paths, defer S3 module loading/config until those helpers are invoked.
    src/lib/response.js:13
  • Static-importing ./s3-service.js causes the module to attempt loading optional @aws-sdk/* peerDependencies during package initialization, which can fail for users that don't install them. To preserve the current "optional S3" behavior, load the S3 service lazily only when getLink()/sendFile(s3://...) are used.
    src/lib/response.js:200
  • After switching to lazy-loading the S3 service, getLink() should resolve the S3 module before invoking getSignedUrl so the AWS SDK peer dependencies are only required when this method is called.
    src/lib/response.js:342
  • After switching to lazy-loading the S3 service, resolve/configure the S3 module before calling getObject() so users without the optional AWS SDK peer dependencies can still use non-S3 sendFile() paths.
    src/index.js:570
  • The CommonJS entrypoint previously set module.exports.default = module.exports to support consumers that access the factory via .default (and to align with the TypeScript default export). With the new module.exports = createAPI assignment, that compatibility property is dropped and can be a breaking change for existing CJS consumers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants